// Projeto desenvolvido para trabalhar com imagem no formato .bmp de 32 bits o valor do pixel
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <omp.h>

// Prototipos das funcoes
uint32_t soma_grupo(uint32_t **a, int32_t m, int32_t n, int pos_m, int pos_n);
uint32_t **alloc_2d_int(int32_t rows, int32_t cols);

int main (int argc, char *argv[]){

    //------------ VARIAVEIS PARA EXECUTAR MPI -------------------------
    int numtasks,          // Numero de processos
    taskid,                // ID do processo
    numworkers,            // Numero de processos que sera dividido as matrizes
    source,                
    dest,                  
    i, j;          

    MPI_Status status;

    MPI_Init(&argc,&argv);	// Inicializa MPI
    MPI_Comm_rank(MPI_COMM_WORLD,&taskid);	// Retorna o ID do processo
    MPI_Comm_size(MPI_COMM_WORLD,&numtasks);	// Retonra a quantida de processos

    // Quanida de processos tem que tem um valor >= 4 senao cancela
    if(numtasks < 4){
	MPI_Finalize();
	printf("\n\nA quantidade de processos tem que ser >= 4.\n\n");
	exit(1);
    }

    numworkers = numtasks-1;	// Numero de processos em que sera dividido o problema


    //----------------- PROCESSO MASTER ---------------------------------
    if (taskid == 0){

	// VARIAVEIS PARA CARREGAR AS IMAGEM
	FILE *fp,*fp1;                     // Ponteiro do arquivo de imagem
    	unsigned char vet_antes[18];       // Vetor que carrega o cabecalho antes daos valores width e height 
    	unsigned char vet_depois[28];      // Vetor que carrega o cabecalho depois daos valores width e height e vai ate chegar antes do primeiro pixel RGBA

        int32_t width;                     // Largura da imagem 
    	int32_t height;                    // Altura da imagem 
    
    	uint32_t **red;                    // Componente vermelha
    	uint32_t **green;                  // Componente verde
    	uint32_t **blue;                   // Componente azul
    
    	uint32_t **smooth_red;             // Componente vermelha apos aplicar smooth
    	uint32_t **smooth_green;           // Componente verde apos aplicar smooth
    	uint32_t **smooth_blue;            // Componente azul apos aplicar smooth

	double ti ,	// tempo inicial
	       tf;	// tempo final
        
	ti = MPI_Wtime();

	fp = fopen(argv[1],"r+b");
    
    	if(fp == NULL)
       	    printf("\n\nErro ao abrir o arquivo. Verificar se ele existe...\n\n");
       
	else{// se o arquivo existir

    	rewind(fp);
    
    	//lendo o cabecalho ate chegar no valor width e heigth
    	for(i=0;i<18;i++)
            fread(&vet_antes[i],sizeof(unsigned char),1,fp);  
    
    	//identificando os valores da largura e alturda da imagem  
    	fread(&width,sizeof(unsigned char),4,fp);  
    	fread(&height,sizeof(unsigned char),4,fp);  
        printf("\n%d Largura da imagem\n%d Altura da imagem\n",width,height);
	//printf("%d Altura da imagem",height);
    
    	//lendo o cabecalho depois dos valores width e heigth até chegar no primeiro pixel RGB
    	for(i=0;i<28;i++)
            fread(&vet_depois[i],1,1,fp);
     
	// Alocando as matrizes RGBA 
        red = alloc_2d_int(height, width);
        green = alloc_2d_int(height, width);
	blue = alloc_2d_int(height, width);

	//Lendo os pixels RGB
        for(i=0;i<height;i++){
            for(j=0;j<width;j++){
                            
                red[i][j] = 0;
                green[i][j] = 0;
           	blue[i][j] = 0;
           
           	fread(&red[i][j],1,1,fp);  
           	fread(&green[i][j],1,1,fp);
           	fread(&blue[i][j],1,1,fp);

       	    } 
    	}
	
	// Enviando para os outros processos
        for (dest=1; dest<=numworkers; dest++){
            
            if(dest == 1){
                MPI_Send(&height, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
		MPI_Send(&width, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
                MPI_Send(&red[0][0], height*width, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
            }
            else if(dest == 2){
		MPI_Send(&height, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
		MPI_Send(&width, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
		MPI_Send(&green[0][0], height*width, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
	    }
            else if(dest == 3){
		MPI_Send(&height, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
		MPI_Send(&width, 1, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
		MPI_Send(&blue[0][0], height*width, MPI_UNSIGNED, dest, 1, MPI_COMM_WORLD);
	    }
       
        }

        // Recebendo as tarefas dos outros processos

        // Alocando as matrizes RGBA auxiliares
        smooth_red = alloc_2d_int(height, width);
        smooth_green = alloc_2d_int(height, width);
	smooth_blue = alloc_2d_int(height, width);        

        for (i=1; i<=numworkers; i++){

            source = i;
	    if(source == 1)
            	MPI_Recv(&smooth_red[0][0], height*width, MPI_UNSIGNED, source, 1, MPI_COMM_WORLD, &status);
	    else if(source == 2)
            	MPI_Recv(&smooth_green[0][0], height*width, MPI_UNSIGNED, source, 1, MPI_COMM_WORLD, &status);
	    else if(source == 3)
            	MPI_Recv(&smooth_blue[0][0], height*width, MPI_UNSIGNED, source, 1, MPI_COMM_WORLD, &status);
       
        }

	// Salvando imagem apos aplicar smooth 
    	fp1 = fopen(argv[2],"wb+");
    
    	for(i=0;i<18;i++)
            fwrite(&vet_antes[i],sizeof(unsigned char),1,fp1);  
       
    	fwrite(&width,sizeof(unsigned char),4,fp1);  
    	fwrite(&height,sizeof(unsigned char),4,fp1);  
    
    	//lendo o cabecalho depois dos valores width e heigth até chegar no primeiro pixel RGB
    	for(i=0;i<28;i++)
            fwrite(&vet_depois[i],1,1,fp1);
       
    	for(i=0;i<height;i++){
            for(j=0;j<width;j++){
           
                fwrite(&smooth_red[i][j],1,1,fp1);  
                fwrite(&smooth_green[i][j],1,1,fp1);
                fwrite(&smooth_blue[i][j],1,1,fp1);
          
       	    } 
    	}

        fclose(fp1); // fechando arquivo de escrita

	tf = MPI_Wtime();
	printf("\nTempo do processamento paralelo: %0.2lf segundos.\n\n",(tf-ti));
	
	}// fim do else se o arquivo existir

	fclose(fp); // fechando arquivo de leitura
	
	
    }

    //--------------- OUTROS PROCESSOS ----------------------------------	

	else if(taskid == 1){

                int32_t width;                     // Largura da imagem 
    		int32_t height;                    // Altura da imagem 

                uint32_t **red;                    // Componente vermelha
    		uint32_t **smooth_red;             // Componente vermelha apos aplicar smooth
                             
		MPI_Recv(&height, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
		MPI_Recv(&width, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
                
		// Alocando as matrizes
                red = alloc_2d_int(height, width);
                smooth_red = alloc_2d_int(height, width);               

        	MPI_Recv(&red[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
		
		//aplicando o smooth para a matriz red
		
    		for(i=0;i<height;i++){
            	    for(j=0;j<width;j++){
           
                        smooth_red[i][j] = soma_grupo(red,height,width,i,j); 
           
            	    } 
    	        }
		
		MPI_Send(&smooth_red[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);
		
	}
	else if(taskid == 2){

		int32_t width;                     // Largura da imagem 
    		int32_t height;                    // Altura da imagem 

                uint32_t **green;                    // Componente verde
    		uint32_t **smooth_green;             // Componente verde apos aplicar smooth
                             
		MPI_Recv(&height, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
		MPI_Recv(&width, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
                
		// Alocando as matrizes
                green = alloc_2d_int(height, width);
                smooth_green = alloc_2d_int(height, width);  		

        	MPI_Recv(&green[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
		
    		for(i=0;i<height;i++){
            	    for(j=0;j<width;j++){
           
                        smooth_green[i][j] = soma_grupo(green,height,width,i,j); 
           
            	    } 
    	        }

		MPI_Send(&smooth_green[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);

	}
	else if(taskid == 3){

		int32_t width;                     // Largura da imagem 
    		int32_t height;                    // Altura da imagem 

                uint32_t **blue;                    // Componente azul
    		uint32_t **smooth_blue;             // Componente azul apos aplicar smooth
                             
		MPI_Recv(&height, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);
		MPI_Recv(&width, 1, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);

		// Alocando as matrizes
                blue = alloc_2d_int(height, width);
                smooth_blue = alloc_2d_int(height, width);		

        	MPI_Recv(&blue[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD, &status);

		//aplicando o smooth para a matriz azul  
		
    		for(i=0;i<height;i++){
            	    for(j=0;j<width;j++){
           
                        smooth_blue[i][j] = soma_grupo(blue,height,width,i,j); 
           
            	    } 
    	        }

		MPI_Send(&smooth_blue[0][0], height*width, MPI_UNSIGNED, 0, 1, MPI_COMM_WORLD);

	}
   
    

    MPI_Finalize();
}

//funcao que calcula a media do grupo 5 x 5
uint32_t soma_grupo(uint32_t **a, int32_t m, int32_t n, int pos_m, int pos_n){
     
     int i,j;
     uint32_t valor = 0;
     //printf("\n");
     for(i=pos_m-2; i<=pos_m+2; i++){
     
         for(j=pos_n-2; j<=pos_n+2; j++){
             
             if(i<0 || j<0 || i>=m || j>=n)                   
                    valor += 0;
                          
             else valor += a[i][j];   
                       
         }            
     }
     
     valor = valor / 25;
     
     return valor;
}

// Funcao que aloca matriz 
uint32_t **alloc_2d_int(int32_t rows, int32_t cols) { 
    int i;
    uint32_t *data = (uint32_t *)malloc(rows*cols*sizeof(uint32_t));
    uint32_t **array= (uint32_t **)malloc(rows*sizeof(uint32_t*));
    for (i=0; i<rows; i++)
        array[i] = &(data[cols*i]);

    return array;
}
